<?php
#
# UNLVSpatial: a dmBridge module for spatial content
#
# Copyright © 2011 Board of Regents of the Nevada System of Higher
# Education, on behalf of the University of Nevada, Las Vegas
#

/**
 * <p>Encapsulates a "spatially enabled" point DMObject. To "spatialize"
 * a given DMObject, call spatializeObject().</p>
 * 
 * <p>Note that this class neither extends nor composes DMObject. Instead, its
 * methods will be "added" to those in the DMObject class when the
 * <code>addAssociatedModel()</code> method is called on the DMObject
 * instance, with an instance of this class supplied as a parameter. (See the
 * user's manual for an explanation of how dmBridge module models work.)</p>
 *
 * @author Alex Dolski <alex.dolski@unlv.edu>
 * @license http://www.opensource.org/licenses/mit-license.php
 */
abstract class UNLVSpatialObjectFactory {

	/**
	 * "Spatializes" the given object by associating a UNLVSpatialObject
	 * instance with it, backed by either the local data store or the given
	 * object's DCMI Point/Box data (which takes precedence). If no data can
	 * be found, no UNLVSpatialObject will be associated with it.
	 * 
	 * @param DMObject obj
	 * @param float lat_n North latitude, returned by reference
	 * @param float lat_s South latitude, returned by reference
	 * @param float long_e East longitude, returned by reference
	 * @param float long_w West longitude, returned by reference
	 * @return void
	 */
	public static function spatializeObject(DMObject $obj) {
		try {
			$obj->getSpatialModelClassName();
			// if that worked, the object is already spatialized
			return;
		} catch (DMNoSuchMethodException $e) {
			// object has no bounds currently associated with it, so continue
		}

		// First, attempt to get DCMI Box bounds data from the object's
		// metadata. We don't know for sure the nick of the field it's in, so
		// try them all.
		foreach ($obj->getMetadata() as $f) {
			$lat = $long = $lat_n = $lat_s = $long_w = $long_e = null;
			try {
				self::parseDCMIPointBounds($f, $lat, $long);
				self::parseDCMIBoxBounds($f, $lat_n, $lat_s, $long_w, $long_e);
				if (!is_null($lat) || !is_null($long)) { // it's a point
					$sp_obj = new UNLVSpatialPointObject();
					$sp_obj->setSpatialDataSource("metadata");
					$sp_obj->setSpatialLat(!is_null($lat) ? $lat : -90);
					$sp_obj->setSpatialLong(!is_null($long) ? $long : 180);
					$obj->addAssociatedModel($sp_obj);
					return;
				} else if (!is_null($lat_n) || !is_null($lat_s)
						|| !is_null($long_w) || !is_null($long_e)) {
					$sp_obj = new UNLVSpatialRectObject();
					$sp_obj->setSpatialDataSource("metadata");
					$sp_obj->setSpatialNorthLat(!is_null($lat_n) ? $lat_n : 90);
					$sp_obj->setSpatialSouthLat(!is_null($lat_s) ? $lat_s : -90);
					$sp_obj->setSpatialWestLong(!is_null($long_w) ? $long_w : -180);
					$sp_obj->setSpatialEastLong(!is_null($long_e) ? $long_e : 180);
					$obj->addAssociatedModel($sp_obj);
					return;
				}
			} catch (DMException $e) {
				// boundary units in this field are unsupported; continue
			}
		}

		// The above failed, so try to fall back on UNLVSpatial data
		$ds = UNLVSpatial::getDataStore();
		$sp_obj = $ds->getObject($obj->getCollection(), $obj->getPtr());
		if ($sp_obj) {
			$obj->addAssociatedModel($sp_obj);
			return;
		}
	}

	/**
	 * Parses the DCMI Box bounding coordinates from the given DMDCElement. If
	 * the parsing fails, the returned-by-reference parameters will be set to
	 * null.
	 * 
	 * @param DMDCElement field
	 * @param float lat_n North latitude, returned by reference
	 * @param float lat_s South latitude, returned by reference
	 * @param float long_e East longitude, returned by reference
	 * @param float long_w West longitude, returned by reference
	 * @return void
	 * @throws DMException if the boundary units are unsupported
	 */
	private static function parseDCMIBoxBounds(DMDCElement $field,
			&$lat_n, &$lat_s, &$long_w, &$long_e) {
		$encoded_bounds = $field->getValue();

		// separate by semicolons
		$parts = explode(";", $encoded_bounds);
		$bounds = array();
		foreach ($parts as $part) {
			// split into key-value pairs
			$kv = explode("=", $part);
			if (count($kv) == 2) {
				$bounds[trim($kv[0])] = trim($kv[1]);
			}
		}

		if (array_key_exists("units", $bounds)) {
			if ($bounds['units'] && $bounds['units'] != "signed decimal degrees") {
				throw new DMException("Unsupported boundary units");
			}
		}

		$n = $s = $e = $w = null;
		if (array_key_exists("northlimit", $bounds)) {
			$n = (float) $bounds['northlimit'];
		}
		if (array_key_exists("southlimit", $bounds)) {
			$s = (float) $bounds['southlimit'];
		}
		if (array_key_exists("westlimit", $bounds)) {
			$w = (float) $bounds['westlimit'];
		}
		if (array_key_exists("eastlimit", $bounds)) {
			$e = (float) $bounds['eastlimit'];
		}
		
		if (!is_null($n) && !is_null($s) && !is_null($w) && !is_null($e)) {
			$lat_n = $n;
			$lat_s = $s;
			$long_w = $w;
			$long_e = $e;
		}
	}

	/**
	 * Parses the DCMI Box bounding coordinates from the given DMDCElement. If
	 * the parsing fails, the returned-by-reference parameters will be set to
	 * null.
	 * 
	 * @param DMDCElement field
	 * @param float lat Latitude, returned by reference
	 * @param float long Longitude, returned by reference
	 * @return void
	 * @throws DMException if the boundary units are unsupported
	 */
	private static function parseDCMIPointBounds(DMDCElement $field,
			&$lat, &$long) {
		$encoded_bounds = $field->getValue();

		// separate by semicolons
		$parts = explode(";", $encoded_bounds);
		$bounds = array();
		foreach ($parts as $part) {
			// split into key-value pairs
			$kv = explode("=", $part);
			if (count($kv) == 2) {
				$bounds[trim($kv[0])] = trim($kv[1]);
			}
		}

		if (array_key_exists("units", $bounds)) {
			if ($bounds['units'] && $bounds['units'] != "signed decimal degrees") {
				throw new DMException("Unsupported boundary units");
			}
		}
		
		$n = $e = null;
		if (array_key_exists("north", $bounds)) {
			$n = (float) $bounds['north'];
		}
		if (array_key_exists("east", $bounds)) {
			$e = (float) $bounds['east'];
		}
		
		if (!is_null($n) && !is_null($e)) {
			$lat = $n;
			$long = $e;
		}
	}

}

